﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JOATServer.SocietyFramework.Core
{
    /// <summary>
    /// The class that is responsible with registering and sending messages to automatons. It implements singleton pattern.
    /// Author: Micu Andrei
    /// </summary>
    [Serializable]
    public class MessageRouter
    {
        protected Dictionary<string, BaseAutomaton> subscribers;
        private static MessageRouter instance;
        public static MessageRouter Instance 
        {
            get
            {
                if (instance == null)
                {
                    instance = new MessageRouter();
                }
                return instance;
            }
        }

        /// <summary>
        /// Gets a list containing the names of the automatons which were subscribed to the MessageRouter.
        /// </summary>
        public IEnumerable<String> SubscriberNames
        {
            get
            {
                return subscribers.Keys;
            }
        }

        protected NetworkMessagingAutomaton networkMessagingAutomaton;

        private MessageRouter()
        {
            subscribers = new Dictionary<String, BaseAutomaton>();
        }

        public void StartNetworkMessaging()
        {
            this.networkMessagingAutomaton = new NetworkMessagingAutomaton(this);
            this.networkMessagingAutomaton.Start();
            MessageRouter.Instance.SendLocal(SocietyInformation.UI_MANAGER_NAME,
                new Message("MessageRouter", "Network messaging started."));
        }

        public void StopNetworkMessaging()
        {
            this.networkMessagingAutomaton.StopSafely(() =>
            {
                MessageRouter.Instance.SendLocal(SocietyInformation.UI_MANAGER_NAME,
                    new Message("MessageRouter", "Network messaging successfully stopped."));
            });
        }

        /// <summary>
        /// Adds the automaton to the list of possible recipients. It is identified by name parameter.
        /// </summary>
        /// <param name="name">the name which this automaton identifies with</param>
        /// <param name="automaton">the automaton to subscribe</param>
        public void Subscribe(string name, BaseAutomaton automaton)
        {
            subscribers.Add(name, automaton);
        }

        /// <summary>
        /// Removes the automaton with the name given as parameter from the subscribers list.
        /// </summary>
        /// <param name="name">the name which this automaton identifies with</param>
        /// <returns>true if operation succeeded</returns>
        public bool Unsubscribe(string name)
        {
            return subscribers.Remove(name);
        }

        /// <summary>
        /// Removes all subscribers from the router.
        /// </summary>
        public void ClearSubscribers()
        {
            subscribers.Clear();
        }

        /// <summary>
        /// Method that sends a message to an automaton specified by the name on the local application.
        /// If there is no automaton on the local machine registered with that name, it is sent on the remote application.
        /// </summary>
        /// <param name="name">name with which the automaton registered</param>
        /// <param name="message">the message</param>
        /// <returns>true if message was sent successfully, false if it was sent on the remote application</returns>
        public bool Send(string name, Message message)
        {
            if (!SendLocal(name, message))
            {
                SendRemote(name, message);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Method that sends a message to all the automatons specified by the names on the local application.
        /// If there is no automaton on the local machine registered with a specific name, it is sent on the remote application.
        /// </summary>
        /// <param name="names">array containing the names with which the automatons registered</param>
        /// <param name="message">the message</param>
        /// <returns>true if all messages were sent successfully, false if at least one was sent on the remote application</returns>
        public bool Send(string[] names, Message message)
        {
            bool success = true;
            for (int i = 0; i < names.Length; i++)
            {
                if (!SendLocal(names[i], message))
                {
                    success = false;
                    SendRemote(names[i], message);
                }
            }
            return success;
        }

        /// <summary>
        /// Method that sends a message to an automaton specified by the name on the local application.
        /// </summary>
        /// <param name="name">name with which the automaton registered</param>
        /// <param name="message">the message</param>
        /// <returns>true if message was sent successfully</returns>
        public bool SendLocal(string name, Message message)
        {
            if (subscribers.ContainsKey(name))
            {
                subscribers[name].AddMessage(message);
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Method that sends a message to all the automatons specified by the names on the local application.
        /// </summary>
        /// <param name="names">array containing the names with which the automatons registered</param>
        /// <param name="message">the message</param>
        /// <returns>true if all messages were sent successfully</returns>
        public bool SendLocal(string[] names, Message message)
        {
            bool success = true;
            for (int i = 0; i < names.Length; i++)
            {
                if (!SendLocal(names[i], message))
                {
                    success = false;
                }
            }
            return success;
        }

        /// <summary>
        /// Method that uses the NetworkMessagingAutomaton to send the message to the remote application.
        /// </summary>
        /// <param name="name">name with which the automaton registered</param>
        /// <param name="message">the message</param>
        public void SendRemote(string name, Message message)
        {
            String[] toData = new String[2];
            toData[0] = name;
            toData[1] = message.Data.ToString();
            Message messageToSend = new Message(message.From, toData);
            networkMessagingAutomaton.AddMessage(messageToSend);
        }
    }
}
